package com.dong.algorithm.novice.introduction;

/**
 * 介绍随机函数
 *
 * @author by jiweidong on 2021/11/19.
 */
public class Arg03_RandomIntroduce {
    public static void main(String[] args) {

        // Java中的Math.random()函数，等概率返回随机数范围: [0, 1)中的一个，double类型
        // 数学上做不到等概率返回一个，但是计算机可以，因为计算机中小数都是有精度的，所以[0, 1)范围内小数不是无穷多的，而是有穷尽的

        System.out.println("======分割线======验证等概率返回");
        // 验证等概率返回
        int testTimes = 1000000;
        int count = 0;
        for (int i = 0; i < testTimes; i++) {
            if (Math.random() < 0.3) {
                count++;
            }
        }
        System.out.println((double) count / (double) testTimes);

        System.out.println("======分割线======改变随机范围后，[0, 1) -> [0, 8)，仍满足等概率返回新范围中的所有数");
        count = 0;
        for (int i = 0; i < testTimes; i++) {
            if (Math.random() * 8 < 4) {
                count++;
            }
        }
        System.out.println((double) count / (double) testTimes);

        System.out.println("======分割线======改变为整数的随机范围:[0, K) -> [0, K-1]，仍满足等概率返回新范围中的所有整数");
        count = 0;
        for (int i = 0; i < testTimes; i++) {
            // [0, 7] 范围内整数等概率随机
            int randomNum = (int) (Math.random() * 8);
            if (randomNum < 4) {
                count++;
            }
//            System.out.println(randomNum);
        }
        System.out.println((double) count / (double) testTimes);

        System.out.println("======分割线======使[0, x)范围内的数出现的概率为x^2");
        count = 0;
        double x = 0.8;
        for (int i = 0; i < testTimes; i++) {
            if (xToXPower2() < x) {
                count++;
            }
        }
        System.out.println((double) count / (double) testTimes);
        System.out.println(Math.pow(x, 2));

        System.out.println("======分割线======同理，使[0, x)范围内的数出现的概率为x^3");
        count = 0;
        x = 0.8;
        for (int i = 0; i < testTimes; i++) {
            if (xToXPower3() < x) {
                count++;
            }
        }
        System.out.println((double) count / (double) testTimes);
        System.out.println(Math.pow(x, 3));


        System.out.println("======分割线======经典面试题测试：随机机制，只能用f1,等概率返回0和1");
        count = 0;
        for (int i = 0; i < testTimes; i++) {
            if (f2() == 0) {
                count++;
            }
        }
        System.out.println((double) count / (double) testTimes);

        System.out.println("======分割线======经典面试题测试：从1~5等概率随机调整到0~7等概率随机（整数）");
        int[] counts = new int[8];
        for (int i = 0; i < testTimes; i++) {
            int num = f3();
            counts[num]++;
        }
        for (int i = 0; i < 8; i++) {
            System.out.println(i + "这个数，出现了 " + counts[i] + " 次");
        }

        System.out.println("======分割线======经典面试题测试：从1~5等概率随机调整到1~7等概率随机（整数）");
        System.out.println("======分割线======经典面试题通用：从a~b随机到c~d随机");
        counts = new int[8];
        for (int i = 0; i < testTimes; i++) {
            int num = g();
            counts[num]++;
        }
        for (int i = 0; i < 8; i++) {
            System.out.println(i + "这个数，出现了 " + counts[i] + " 次");
        }

        System.out.println("======分割线======经典面试题测试：0、1不等概率随机调整到0、1等概率随机");
        counts = new int[2];
        for (int i = 0; i < testTimes; i++) {
            int num = y();
            counts[num]++;
        }
        for (int i = 0; i < 2; i++) {
            System.out.println(i + "这个数，出现了 " + counts[i] + " 次");
        }
    }

    // 返回[0, 1)的一个小数
    // 对于任意的x，x属于[0, 1)，[0, x)范围上的数出现的概率由原来的x调整成x平方
    public static double xToXPower2() {
        return Math.max(Math.random(), Math.random());
    }

    // 同理，[0, x)范围上的数出现的概率由原来的x调整成x的3次方
    public static double xToXPower3() {
        return Math.max(Math.random(), Math.max(Math.random(), Math.random()));
    }

    // 从1~5等概率随机的函数（看成黑盒的或lib里的函数不能改，然后由这个函数调整到其他的随机范围）
    public static int f1() {
        return (int) (Math.random() * 5) + 1;
    }

    // 随机机制，只能用f1,
    // 等概率返回0和1（0、1等概率发生器）
    public static int f2() {
        int ans;
        do {
            ans = f1();
        } while (ans == 3);
        return ans < 3 ? 0 : 1;
    }

    // 得到 000 ~ 111 做到等概率
    // 这个函数其实就做到了0~7等概率返回一个
    public static int f3() {
//        return (f2() << 2) + (f2() << 1) + (f2() << 0);
        return (f2() << 2) + (f2() << 1) + (f2());
    }

    // 做到0~6等概率返回一个
    public static int f4() {
        int ans;
        do {
            ans = f3();
        } while (ans == 7);
        return ans;
    }

    // 做到1~7等概率返回一个
    public static int g() {
        return f4() + 1;
    }

    // 你只能知道，x会以固定概率返回0和1，但是x的内容，你看不到
    public static int x() {
        return Math.random() < 0.84 ? 0 : 1;
    }

    // 经典面试题：由0、1不等概率随机调整到0、1等概率随机
    public static int y() {
        int ans;
        // 执行两次x()，如果第一次等于第二次，则重做
        do {
            ans = x();
        } while (ans == x());
        // ans = 0  1
        // ans = 1  0
        return ans;
    }

}
